返回设计模式博客目录
|
第一篇:单一职责原则
第二篇:开闭原则
第三篇:里氏替换原则
第四篇:依赖倒置原则
第五篇:接口隔离原则
第六篇:迪米特原则
请使用双缓存技术(内存、SD 卡)继续优化图片加载器?
第一篇中已经实现了内存缓存类 ImageCache.java,我们还需要增加一个 SD 卡缓存类 DiskCache.java。
然后修改 ImageLoader.java 源码进行测试,可使用:
接下来要实现的是:首先使用内存缓存,如果内存缓存没有图片再使用 SD 卡缓存,如果 SD 卡中也没有图片,最后才从网络上获取。
于是新建一个双缓存类 DoubleCache.java,源码如下:
虽然双缓存技术很优秀,但是我们最好提供 API,让使用者可以灵活选择缓存方式:只选内存缓存、只选 SD 卡缓存或者选择双缓存方式。而不合格的程序员则会提供如下代码:
上述代码中,要加入新的缓存实现时都需要修改 ImageLoader 类,然后通过一个布尔变量让用户选择使用哪种缓存。因此,就使得在 ImageLoader 中存在各种 if-else 判断语句,通过这些判断来确定使用哪种缓存。随着这些逻辑的引入,代码越来越复杂、脆弱。如果不小心写错了某个 if 条件,那就需要更多的时间来排除,整个 ImageLoader 类也会变得越来越臃肿。最重要的是,用户不能自己实现缓存注入到 ImageLoader 中,可扩展性差。
软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的,这就是开放——关闭原则。
也就是说,当软件需要变化时,我们应该尽量通过扩展的方式实现变化,而不是通过修改已有的代码来实现。根据这一个原则,我们可以画出如下所示的 UML 图。
按照上图进行以下重构:
提取抽象接口,用来抽象图片缓存的功能。其声明如下:
ImageCache 接口简单定义了获取、缓存图片两个函数,缓存的 key 是图片的 url,值是图片本身。内存缓存、SD 卡缓存、双缓存都实现了该接口,我们看看这几个缓存实现。
然后重构 ImageLoader,如下所示:
附工具类 ImageUtil.java
经过此次重构,没有了那么多的 if-else 语句,没有了各种各样的缓存实现对象、布尔变量,代码确实清晰简洁。用户可以通过setImageCache(ImageCache cache) 函数设置缓存实现,也就是通常说的依赖注入。具体如下所示:
在上述代码中,通过 setImageCache(ImageCache cache) 方法注入不同的缓存实现,这样不仅能够使 ImageLoader 更简单、健壮,也使得 ImageLoader 的可扩展性、灵活性更高。MemoryCache、DiskCache、DoubleCache 缓存图片的具体实现完全不一样,但是,它们的一个特点是,都实现了 ImageCache 接口。当用户需要自定义实现缓存策略时,只需要新建一个实现 ImageCache 接口的类,然后构造该类的对象,并且通过 setImageCache 函数注入到 ImageLoader 中,这样 ImageLoader 就实现了千变万化的缓存策略,且扩展这些缓存策略并不会导致 ImageLoader 类的修改。
开闭原则指导我们,当软件需要变化时,应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。但是通过继承等方式添加新的实现,这会导致类型的膨胀以及历史遗留代码的冗余。在开发过程中需要自己结合具体情况进行考量。
开闭原则概述
软件实体应当对扩展开放,对修改关闭 (Software entities should be open for extension, but closed for modification)。
开闭原则是面向对象设计中“可复用设计”的基石,是设计模式最基本的法则。其他五大设计原则和 23 种设计模式都可以看做是开闭原则的实现方法和手段。
说的通俗一点就是,已经开发好的软件实体(如类、模块、函数),在升级迭代引入新功能时,不应该修改已有的代码,而是在已有代码的基础上,添加新代码来实现。